const acorn = require("acorn");
const walk = require("acorn-walk");
const fs = require("fs");
// 未匹配到文件路径，则退出校验
if (!process.argv[2]) process.exit(0);
const filePath = process.argv[2].trim().split("=")[1];
let jsContent = fs.readFileSync(filePath).toString();
let matchJsContent = "";
// 对于Vue文件，匹配script内的脚本
jsContent = jsContent.replace(
  /<script>([\S\s]+?)<\/script>/g,
  function (val, $0) {
    matchJsContent = $0;
  }
);

const ast = acorn.parse(matchJsContent, {
  ecmaVersion: 2020,
  sourceType: "module",
});

class CheckWatch {
  constructor() {
    this.errors = [];
    this.init();
  }
  // 在nodejs终端输出带颜色字体
  print(color, msg) {
    let map = {
      bright: "\x1B[1m", // 亮色
      grey: "\x1B[2m", // 灰色
      italic: "\x1B[3m", // 斜体
      underline: "\x1B[4m", // 下划线
      reverse: "\x1B[7m", // 反向
      hidden: "\x1B[8m", // 隐藏
      black: "\x1B[30m", // 黑色
      red: "\x1B[31m", // 红色
      green: "\x1B[32m", // 绿色
      yellow: "\x1B[33m", // 黄色
      blue: "\x1B[34m", // 蓝色
      magenta: "\x1B[35m", // 品红
      cyan: "\x1B[36m", // 青色
      white: "\x1B[37m", // 白色
      blackBG: "\x1B[40m", // 背景色为黑色
      redBG: "\x1B[41m", // 背景色为红色
      greenBG: "\x1B[42m", // 背景色为绿色
      yellowBG: "\x1B[43m", // 背景色为黄色
      blueBG: "\x1B[44m", // 背景色为蓝色
      magentaBG: "\x1B[45m", // 背景色为品红
      cyanBG: "\x1B[46m", // 背景色为青色
      whiteBG: "\x1B[47m", // 背景色为白色
    };
    console.log(map[color] || map["bright"], msg, "\x1b[0m");
  }
  init() {
    this.checkCode();
    if (this.errors.length) {
      let errorDescArr = this.errors.reduce((total, curr) => {
        let { msg } = curr;
        total.push(msg);
        return total;
      }, []);
      errorDescArr.unshift(`========== 文件路径：${filePath} ==========`);
      this.print("red", errorDescArr.join("\n "));
      process.exit(1);
    }
  }
  checkCode() {
    let errors = [];
    function _validateSetInterval(node, ancestors) {
      // 判断setInterval是否是写在watch对应的对象内
      const isInludeWatchProperty = ancestors.find(
        (ancestor) =>
          ancestor.type === "Property" && ancestor.key.name === "watch"
      );
      const isInludeByIfStatement = ancestors.find(
        (ancestor) =>
          ancestor.type === "IfStatement" &&
          ancestor.test.type === "BinaryExpression" &&
          ancestor.test.left.type === "UnaryExpression" &&
          ancestor.test.left.operator === "typeof" &&
          ancestor.test.left.argument.name === "window" &&
          ((ancestor.test.operator === "===" &&
            ancestor.test.right.value === "object") ||
            (ancestor.test.operator === "!==" &&
              ancestor.test.right.value === "undefined"))
      );
      // 只检测watch对象中的setInterval情况
      if (!isInludeWatchProperty) {
        return;
      }
      if (!isInludeByIfStatement) {
        errors.push({
          msg: `检测到watch中使用了setInterval，未使用“if (typeof window === 'object') {}”或“if (typeof window !== 'undefined') {}”包裹，请修改代码`,
        });
      }
    }
    walk.ancestor(ast, {
      CallExpression(node, ancestors) {
        if (node.callee.name === "setInterval") {
          _validateSetInterval(node, ancestors);
        }
      },
      MemberExpression(node, ancestors) {
        if (
          node.object.name === "window" &&
          node.property.name === "setInterval"
        ) {
          _validateSetInterval(node, ancestors);
        }
      },
    });
    this.errors = errors;
  }
}

new CheckWatch();
